#include "prologin.hh"
#include <bits/stdc++.h>
using namespace std;

set<position> mine;
set<position> goals;
set<position> reinforceTodo;

vector<position> surrounding(const position&);

bool operator!=(const position& a, const position& b) {
    return (a.x != b.x || a.y != b.y);
}

template <typename T>
struct UF {
    map<T, T> p;

    T parent(T a) {
        if (p.count(a) == 0)
            return a;
        return p[a] = parent(p[a]);
    }

    bool same(T a, T b) {
        return parent(a) == parent(b);
    }

    void join(T a, T b) {
        if (!same(a, b))
        {
            p[parent(b)] = parent(a);
        }
    }
};

unsigned int H[TAILLE_TERRAIN][TAILLE_TERRAIN];

void fill_heuristic() {
    memset(H, -1, sizeof(H));
    for (int x = 0; x < TAILLE_TERRAIN; x++)
        for (int y = 0; y < TAILLE_TERRAIN; y++)
            for (const auto& p : liste_pulsars())
                H[x][y] = min(H[x][y], unsigned(abs(x - p.x) + abs(y - p.y)));
}

/// Fonction appelée au début de la partie.
void partie_init()
{
  // fonction a completer
  srand(time(NULL));

  fill_heuristic();

  for (const auto& b : ma_base())
      mine.insert(b);

  for (const auto& p : liste_pulsars()) {
    for (const auto& k : surrounding(p))
        goals.insert(k);
  }
}

void swap(position& p, position& b) {
    swap(p.x, b.x);
    swap(p.y, b.y);
}

vector<position> surrounding(const position& p) {
    vector<position> r;
    for (int dx = -1; dx < 2; dx++)
        for (int dy = -1; dy < 2; dy++)
            if (abs(dx + dy) == 1)
                r.push_back(position{p.x + dx, p.y + dy});
    return r;
}

double calcAdvantage(const position& p, int dist) {
    double ret = 0;
    for (int dx = -1; dx < 2; dx++)
        for (int dy = -1; dy < 2; dy++)
            if (abs(dx + dy) == 1 && type_case(position{p.x + dx, p.y + dy}) == PULSAR)
            {
                auto info = info_pulsar(position{p.x + dx, p.y + dy});
                int puls_before_there = 0;
                for (int i = 0; i < dist; i++)
                    puls_before_there += (tour_actuel() + i + 1) % info.periode == 0;
                ret += (info.pulsations_restantes - puls_before_there) * info_pulsar(position{p.x + dx, p.y + dy}).puissance;
            }
    return ret;
}

void reinforce() {
    while (points_action() && reinforceTodo.size()) {
        position pulsar = *reinforceTodo.begin();
        reinforceTodo.erase(reinforceTodo.begin());
        queue<position> q;
        q.push(pulsar);
        map<position, position> parent;
        parent[pulsar] = pulsar;

        position p;
        while (!q.empty()) {
            p = q.front();
            q.pop();

            if (type_case(p) == BASE && proprietaire_base(p) == moi()) {
                break;
            }

            for (int dx = -1; dx < 2; dx++)
                for (int dy = -1; dy < 2; dy++)
                    if (abs(dx + dy) == 1)
                    {
                        position n = {p.x + dx, p.y + dy};
                        if ((est_libre(n) || est_tuyau(n)) && parent.find(n) == parent.end() && (find(mine.begin(), mine.end(), n) == mine.end() || type_case(n) == BASE)) {
                            q.push(n);
                            parent[n] = p;
                        }
                    }
        }

        position fin = p;
        cout << pulsar.x << " " << pulsar.y << endl;

        stack<position> s;
        if (type_case(p) == BASE && proprietaire_base(p) == moi()) {
            while (p != parent[p]){
                p = parent[p];
                s.push(p);
            }
            while (s.size() > 1) {
                if (construire(s.top()) == OK) {
                    mine.insert(s.top());
                    goals.erase(s.top());
                }
                else if (!points_action())
                {
                    reinforceTodo.insert(s.top());
                    return;
                }
                else
                    assert(false);
                s.pop();
            }
        }
        else
            assert(false);
    }
}

void aS() {
    using ii = pair<int, int>;
    using T = pair<ii, position>;
    priority_queue<T, deque<T>, greater<T> > q;
    map<position, int> cost;
    map<position, position> parent;

    for (const auto& p : mine)
    {
        q.push(T(ii(H[p.x][p.y], 0), p));
        parent[p] = p;
        cost[p] = 0;
    }

    position p{-1, -1};
    position bestcost{-1, -1};
    double _bestcost = 0;
    while (!q.empty()) {
        T item = q.top();
        q.pop();
        int c = item.first.second;
        p = item.second;

        if (goals.count(p) > 0)
        {
            double adv = calcAdvantage(p, c / 4);
            if (type_case(p) == DEBRIS)
                adv = 10000;

            if (adv > _bestcost)
            {
                bestcost = p;
                _bestcost = adv;
            }
        }

        for (int dx = -1; dx < 2; dx++)
            for (int dy = -1; dy < 2; dy++)
                if (dx == 0 && dy != 0 || dx != 0 && dy == 0)
                {
                    if (cost.count(position{p.x + dx, p.y + dy}) == 0 || cost[position{p.x + dx, p.y + dy}] > c) {
                        switch (type_case(position{p.x + dx, p.y + dy})) {
                            case VIDE: case TUYAU: case SUPER_TUYAU:
                                cost[position{p.x + dx, p.y + dy}] = c;
                                parent[position{p.x + dx, p.y + dy}] = p;
                                q.push(T(ii(c + 1 + H[p.x + dx][p.y + dy], c + 1), position{p.x + dx, p.y + dy}));
                                break;
                            case DEBRIS:
                                cost[position{p.x + dx, p.y + dy}] = c;
                                parent[position{p.x + dx, p.y + dy}] = p;
                                q.push(T(ii(c + 3 + H[p.x + dx][p.y + dy], c + 3), position{p.x + dx, p.y + dy}));
                                break;
                        }
                    }
                }
    }

    p = bestcost;

    stack<position> s;
    while (parent.count(bestcost) > 0 && bestcost != parent[bestcost]) {
        s.push(bestcost);
        bestcost = parent[bestcost];
    }

    if (type_case(bestcost) == BASE)
    {
        reinforceTodo.insert(p);
    }

    while (!s.empty()) {
        p = s.top();
        if (type_case(p) == DEBRIS && deblayer(p) == OK && construire(p) == OK)
        {
            goals.erase(p);
            mine.insert(p);
        }
        else if (type_case(p) == DEBRIS)
        {
            goals.insert(p);
        }
        else if (construire(p) == OK || est_tuyau(p))
        {
            goals.erase(p);
            mine.insert(p);
        }
        else
        {
            break;
        }

        s.pop();
    }
}

void repare() {
    vector<position> ps = hist_tuyaux_detruits();
    if (!ps.empty()) {
        if (deblayer(ps[0]) == OK && construire(ps[0]) == OK)
            return;
        else
            goals.insert(ps[0]);
    }
}

void dummy() {
    for (const auto& b : ma_base()) {
        if (b.x == 38 || b.x == 0)
        {
            for (int k = 0; k < 13; k++)
            {
                if (construire(position{k + 1, 13}) == OK)
                    mine.insert(position{k + 1, 13}), goals.erase(position{k + 1, 13});
            }
            for (int k = 0; k < 13; k++)
            {
                if (construire(position{k + 1, 25}) == OK)
                    mine.insert(position{k + 1, 25}), goals.erase(position{k + 1, 25});
            }
            for (int k = 36; k > 23; k--)
            {
                if (construire(position{k + 1, 13}) == OK)
                    mine.insert(position{k + 1, 13}), goals.erase(position{k + 1, 13});
            }
            for (int k = 36; k > 23; k--)
            {
                if (construire(position{k + 1, 25}) == OK)
                    mine.insert(position{k + 1, 25}), goals.erase(position{k + 1, 25});
            }
            break;
        }
        else if (b.y == 38 || b.y == 0)
        {
            for (int k = 0; k < 13; k++)
            {
                if (construire(position{13, k + 1}) == OK)
                    mine.insert(position{13, k + 1}), goals.erase(position{13, k + 1});
            }
            for (int k = 0; k < 13; k++)
            {
                if (construire(position{25, k + 1}) == OK)
                    mine.insert(position{25, k + 1}), goals.erase(position{25, k + 1});
            }
            for (int k = 36; k > 23; k--)
            {
                if (construire(position{13, k + 1}) == OK)
                    mine.insert(position{13, k + 1}), goals.erase(position{13, k + 1});
            }
            for (int k = 36; k > 23; k--)
            {
                if (construire(position{25, k + 1}) == OK)
                    mine.insert(position{25, k + 1}), goals.erase(position{25, k + 1});
            }
            break;
        }
    }
}

void ameliore() {
    int done = 0;
    while (points_action() && done < 15) {
        int r = rand() % mine.size();
        auto it = mine.begin();
        for (; it != mine.end() && r >= 0; ++it, r--);
        ameliorer(*it);
        ++done;
    }
}

int canDestroy(const position& p, int depth, set<position>& used) {
    if (depth <= 0) return 0;
    used.insert(p);
    int sum = charges_presentes(p);
    for (const auto& a : surrounding(p)) {
        if (est_tuyau(a) && used.count(a) == 0) {
            sum += canDestroy(a, depth - 1, used);
        }
    }
    return sum;
}

int canDestroy(const position& p, int depth=10) {
    set<position> used;
    return canDestroy(p, depth, used);
}

int numBasesReachable(const position& p) {
    //TODO
}

void destroy() {
    for (const auto& b : base_ennemie()) {
        for (const auto& p : surrounding(b)) {
            if (est_tuyau(p)) {
                int count = 0;
                for (const auto& a : surrounding(p)) {
                    if (est_tuyau(a))
                        count++;
                }
                if (count == 1 && canDestroy(p) > 7 && numBasesReachable(p) <= 1)
                    detruire(p);
            }
        }
    }
}

bool tuyau_connecte(const position& p) {
    for (const auto& a : surrounding(p))
        if (est_tuyau(a))
            return true;
    return false;
}

void reallocForces() {
    for (auto& c : ma_base()) {
        if (tuyau_connecte(c)) {
            for (const auto& b : ma_base()) {
                if (puissance_aspiration(b) && !tuyau_connecte(b)) {
                    if (deplacer_aspiration(b, c) == OK)
                    {
                        return;
                    }
                }
            }
        }
    }
}

/// Fonction appelée à chaque tour.
void jouer_tour()
{
    auto t = chrono::high_resolution_clock::now();
    cout << reinforceTodo.size() << endl;
    reinforce();
    aS();
    reallocForces();
    repare();
    destroy();
    dummy();
    ameliore();
    chrono::duration<double> duration = chrono::high_resolution_clock::now() - t;
    cout << duration.count() << endl;
}

/// Fonction appelée à la fin de la partie.
void partie_fin()
{
  // fonction a completer
  if (score(moi()) > score(adversaire())) {
      cout << "Gagné!\n  (42)" << endl;
  }
  else {
      cout << "J'ai perdu" << endl;
  }
}

